package eval

import (
	"showen7/eval"
	"showen7/inter"
	"showen7/lex"
	"showen7/parse"
	"testing"
)

func TestEvalIf(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		{
			"(if #t 1 2)",
			[]string{"1"},
		},
		{
			"(if #f 1 2)",
			[]string{"2"},
		},
		{
			"(if #t 1)",
			[]string{"1"},
		},
		{
			"(if #f 1)",
			[]string{""}, // Should return null
		},
		{
			"(if (= 5 5) 'equal 'not-equal)",
			[]string{"equal"},
		},
		{
			"(if (= 5 3) 'equal 'not-equal)",
			[]string{"not-equal"},
		},
		{
			"(if (> 10 5) (+ 3 4) (- 10 3))",
			[]string{"7"},
		},
		{
			"(if (< 10 5) (+ 3 4) (- 10 3))",
			[]string{"7"},
		},
		{
			"(define x 5) (if (> x 3) (* x 2) (/ x 2))",
			[]string{"", "10"},
		},
		{
			"(define y 1) (if (< y 3) (* y 10) (/ y 10))",
			[]string{"", "10"},
		},
		{
			"(if (not #f) 'true 'false)",
			[]string{"true"},
		},
		{
			"(if (not #t) 'true 'false)",
			[]string{"false"},
		},
		{
			"(if (and #t #f) 'true 'false)",
			[]string{"false"},
		},
		{
			"(if (or #t #f) 'true 'false)",
			[]string{"true"},
		},
	}

	env := inter.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		for i, expr := range exprs {
			var o inter.Object
			o, err = eval.EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			expected := tt.want[i]
			actual := o.Inspect()

			// Handle null object which is represented as empty string
			if o.Type() == inter.NULL_OBJ {
				actual = ""
			}

			if actual != expected {
				t.Errorf("Input: %s, Expression %d: EvalExpression() = %v, want %v", tt.input, i, actual, expected)
			}
		}
	}
}

func TestEvalIfErrorCases(t *testing.T) {
	errorTests := []struct {
		input         string
		expectedError string
	}{
		{
			"(if)",
			"if requires at least 2 arguments",
		},
		{
			"(if #t)",
			"if requires at least 2 arguments",
		},
		{
			"(if undefined-var #t #f)",
			"identifier not found: undefined-var",
		},
	}

	env := inter.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	for _, tt := range errorTests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		for _, expr := range exprs {
			_, err = eval.EvalExpression(expr, env)
			if err == nil {
				t.Errorf("Input: %s, expected error but got none", tt.input)
				continue
			}

			if err.Error() != tt.expectedError {
				t.Errorf("Input: %s, expected error %q, got %q", tt.input, tt.expectedError, err.Error())
			}
		}
	}
}

func TestEvalCond(t *testing.T) {
	tests := []struct {
		input string
		want  []string
	}{
		{
			"(cond (#t 1))",
			[]string{"1"},
		},
		{
			"(cond (#f 1) (#t 2))",
			[]string{"2"},
		},
		{
			"(cond (#f 1) (#f 2) (else 3))",
			[]string{"3"},
		},
		{
			"(cond ((= 1 1) 'equal) (else 'not-equal))",
			[]string{"equal"},
		},
		{
			"(cond ((= 1 2) 'equal) (else 'not-equal))",
			[]string{"not-equal"},
		},
		{
			"(cond ((> 5 3) 'greater) ((< 5 3) 'less))",
			[]string{"greater"},
		},
		{
			"(cond ((> 5 10) 'greater) ((< 5 10) 'less))",
			[]string{"less"},
		},
		{
			"(cond ((> 5 10) 'greater) ((< 5 10) 'less) (else 'equal))",
			[]string{"less"},
		},
		{
			"(cond ((> 5 3) 'greater) ((< 5 3) 'less) (else 'equal))",
			[]string{"greater"},
		},
		{
			"(cond ((> 5 5) 'greater) ((< 5 5) 'less) (else 'equal))",
			[]string{"equal"},
		},
		{
			"(cond (#t 1 2 3))",
			[]string{"3"},
		},
		{
			"(cond (#f 1) (#t 2 3 4))",
			[]string{"4"},
		},
		{
			"(define x 10) (cond ((> x 5) (* x 2)) ((< x 5) (/ x 2)))",
			[]string{"", "20"},
		},
		{
			"(define y 1) (cond ((> y 5) (* y 2)) ((< y 5) (/ y 1)))",
			[]string{"", "1"},
		},
		{
			"(cond ((not #f) 'true) (else 'false))",
			[]string{"true"},
		},
		{
			"(cond ((not #t) 'true) (else 'false))",
			[]string{"false"},
		},
		{
			"(cond ((and #t #t) 'true) (else 'false))",
			[]string{"true"},
		},
		{
			"(cond ((and #t #f) 'true) (else 'false))",
			[]string{"false"},
		},
		{
			"(cond ((or #f #f) 'true) (else 'false))",
			[]string{"false"},
		},
		{
			"(cond ((or #t #f) 'true) (else 'false))",
			[]string{"true"},
		},
		{
			"(cond (#f 1))",
			[]string{""}, // No matching clause, should return null
		},
		{
			"(cond ((= 1 2) (+ 1 2)) ((= 2 3) (+ 3 4)) ((= 3 4) (+ 5 6)))",
			[]string{""}, // No matching clause, should return null
		},
	}

	env := inter.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		for i, expr := range exprs {
			var o inter.Object
			o, err = eval.EvalExpression(expr, env)
			if err != nil {
				t.Fatalf("EvalExpression() error = %v", err)
			}

			expected := tt.want[i]
			actual := o.Inspect()

			// Handle null object which is represented as empty string
			if o.Type() == inter.NULL_OBJ {
				actual = ""
			}

			if actual != expected {
				t.Errorf("Input: %s, Expression %d: EvalExpression() = %v, want %v", tt.input, i, actual, expected)
			}
		}
	}
}

func TestEvalCondErrorCases(t *testing.T) {
	errorTests := []struct {
		input         string
		expectedError string
	}{
		{
			"(cond)",
			"cond requires at least one clause",
		},
	}

	env := inter.NewEnvironment()
	eval.RegisterBuiltinProcedures(env)

	for _, tt := range errorTests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		exprs, err := parser.ParseProgram()
		if err != nil {
			t.Fatalf("parser.ParseProgram() error = %v", err)
		}

		for _, expr := range exprs {
			_, err = eval.EvalExpression(expr, env)
			if err == nil {
				t.Errorf("Input: %s, expected error but got none", tt.input)
				continue
			}

			if err.Error() != tt.expectedError {
				t.Errorf("Input: %s, expected error %q, got %q", tt.input, tt.expectedError, err.Error())
			}
		}
	}
}
